<?php if (!defined('CORE_DIR')) die('Wrong entry point!');

/* 
 * Copyright 2010 Anton Muraviev a.k.a kuromaisu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */


// =============================================================================
// Section: Response
// =============================================================================
// 
// Configuration namespaces:
// 	response, errors
// 
// Configuration options:
// 	response.default_charset		-	utf-8 by default
// 	response.default_content_type	-	text/html by default
// 	response.compression_level		-	an integer (0-9); 0 (default value) means
// 										no compression
// 	errors.template					-	general error template

Aero::define('AeroResponse', AS_SINGLETON);

/* -----------------------------------------------------------------------------
 * Class: AeroResponse
 * -----------------------------------------------------------------------------
 * 
 * The purpose of this class is to simplify caching and compression of various 
 * types of content. So you should only use this class for HTTP/1.1 200 Ok and 
 * 301 or 302 redirects.
 * 
 */
class AeroResponse
{
	private $status = 200;
	private $type = 'text/html';
	private $charset = 'utf-8';
	
	protected $output = '';
	protected $headers = array();
	
	protected $ttl = 0;
	protected $cache_on_client = false;
	protected $cache_on_server = false;
	protected $cache_on_proxy = false;
	
	protected $http_statuses = array(
		100 => '100 Continue',
		101 => '101 Switching Protocols',

		//  Success
		200 => '200 OK',
		201 => '201 Created',
		202 => '202 Accepted',
		203 => '203 Non-Authoritative Information',
		204 => '204 No Content',
		205 => '205 Reset Content',
		206 => '206 Partial Content',

		//  Redirects
		300 => '300 Multiple Choices',
		301 => '301 Moved Permanently',
		302 => '302 Found',
		303 => '303 See Other',
		304 => '304 Not Modified',
		305 => '305 Use Proxy',
		306 => '306 (Unused)',
		307 => '307 Temporary Redirect',

		// Client errors
		400 => '400 Bad Request',
		401 => '401 Unauthorized',
		402 => '402 Payment Required',
		403 => '403 Forbidden',
		404 => '404 Not Found',
		405 => '405 Method Not Allowed',
		406 => '406 Not Acceptable',
		407 => '407 Proxy Authentication Required',
		408 => '408 Request Timeout',
		409 => '409 Conflict',
		410 => '410 Gone',
		411 => '411 Length Required',
		412 => '412 Precondition Failed',
		413 => '413 Request Entity Too Large',
		414 => '414 Request-URI Too Long',
		415 => '415 Unsupported Media Type',
		416 => '416 Requested Range Not Satisfiable',
		417 => '417 Expectation Failed',

		// Server errors
		500 => '500 Internal Server Error',
		501 => '501 Not Implemented',
		502 => '502 Bad Gateway',
		503 => '503 Service Unavailable',
		504 => '504 Gateway Timeout',
		505 => '505 HTTP Version Not Supported'
	);
	
	/*
	 * Constructor: __construct
	 */
	public function __construct()
	{
		$this->clean();
	}
	
	/*
	 * Destructor
	 * 
	 * When script quits display() method is called.
	 * 
	 */
	public function __destruct()
	{
		if (!is_null($this->output))
		{
			$this->display();
		}
	}
	
	/*
	 * Method: setHeader
	 * 
	 * 
	 * 
	 */
	public function setHeader($name, $value = null, $replace = true)
	{
		if (is_null($value))
		{
			$value = $name;
			$name = '';
			$replace = false;
		}
		
		if (isset($this->headers[$name]) && $replace === false)
		{
			$this->headers[$name][] = $value;
		}
		else
		{
			$this->headers[$name] = array($value);
		}
	}
	
	/*
	 * Method: header
	 *
	 */
	public function header($name)
	{
		return isset($this->headers[$name]) ? $this->headers[$name] : array();
	}
	
	/*
	 * Method: headers
	 * 
	 * Returns all headers.
	 *
	 */
	public function headers()
	{
		return $this->headers;
	}
	
	/*
	 * Method: setOutput
	 *
	 */
	public function setOutput($content)
	{
		$this->output = $content;
	}
	
	/*
	 * Method: output
	 *
	 */
	public function output()
	{
		return $this->output;
	}
	
	/*
	 * Method: setContentType
	 *
	 */
	public function setContentType($type)
	{
		$type = strtolower($type);
		
		switch ($type)
		{
			case 'html':
				$this->type = 'text/html';break;
			case 'css':
			case 'stylesheet':
				$this->type = 'text/css';break;
			case 'javascript':
			case 'json':
			case 'js':
				$this->type = 'text/javascript';break;
			case 'text':
				$this->type = 'text/plain';break;
			case 'atom':
			case 'rdf':
			case 'rss':
			case 'xhtml':
				$this->type = 'application/'.$type.'+xml';break;
			case 'xml':
				$this->type = 'application/xml';break;
			default:
				$this->type = $type;
		}
	}
	
	/*
	 * Method: contentType
	 *
	 */
	public function contentType()
	{
		return $this->type;
	}
	
	/*
	 * Method: setStatus
	 *
	 */
	public function setStatus($status)
	{
		if (is_numeric($status))
		{
			$this->status = (int)$status;
		}
	}
	
	/*
	 * Method: status
	 *
	 */
	public function status()
	{
		return $this->status;
	}

	/*
	 * Method: clean
	 *
	 */
	public function clean()
	{
		$this->headers = array();
		$this->output = '';
		
		$this->cache_on_client = false;
		$this->cache_on_server = false;
		$this->cache_on_proxy = false;
		$this->ttl = 0;

		$this->status = 200;
		$this->type = Aero::option('response.default_content_type','text/html');
		$this->charset = Aero::option('response.default_charset','utf-8');
	}
	
	/*
	 * Method: display
	 *
	 */
	public function display()
	{
		// HTTP status response 
		if ($this->status !== 200 && isset($this->http_statuses[$this->status]))
		{
			$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
			header($protocol . ' ' . $this->http_statuses[$this->status]);
		}
		
		// Content comression
		$compression_level = Aero::option('response.compression_level', 0);
		
		if ($compression_level > 0)
		{
			$output = $this->_gzip($this->output,$compression_level);
		}
		else
		{
			$output = $this->output;
		}
		
		// HTTP caching
		$this->_compileCachingHeaders();
		
		// Other headers
		$this->setHeader('Content-type', $this->type . '; charset='.$this->charset);
		$this->setHeader('Content-length', strlen($output));
		
		foreach ($this->headers as $name => $value)
		{
			$prefix = empty($name) ? '' : $name . ': ';
			
			header($prefix . array_shift($value));
			
			foreach ($value as $_value)
			{
				header($prefix . $_value, false);
			}
		}
		
		// Spit the output
		echo $output;
		
		// Prevent the destructor from displaying the content again.
		$this->output = null;
		
		exit;
	}
	
	/*
	 * Method: render
	 *
	 */
	public function render($path, $parameters = array())
	{
		$view = Aero::load('libs/view',$parameters);
		
		$this->output = $view->load($path);
	}
	
	/*
	 * Method: cache
	 *
	 */
	public function cache($ttl, $how = 'client server')
	{
		$Request = Aero::load('libs/request');
		
		if (!$Request->is('GET'))
		{
			return;
		}
		
		$this->ttl = is_numeric($ttl) && $ttl > 0 ? $ttl : 0;
		
		if ($this->ttl === 0)
		{
			return;
		}
		
		$how = str_replace(array('_','-'),'',strtolower($how));
		
		if ($how === 'all')
		{
			$this->cache_on_client = true;
			$this->cache_on_server = true;
			$this->cache_on_proxy = true;
			
			return;
		}
		
		if (strpos($how, 'server') !== false)
		{
			$this->cache_on_server = true;
		}
		
		if (strpos($how, 'client') !== false || strpos($how, 'browser') !== false)
		{
			$this->cache_on_client = true;
		
			if (strpos($how, 'proxy') !== false)
			{
				$this->cache_on_proxy = true;
			}
		}
	}
	
	/*
	 * Method: tryCache
	 * 
	 * Tries to return cached response. Halts execution, if successful.
	 *
	 */
	public function tryCache()
	{
		# code...
	}
	
	/*
	 * Method: redirect
	 */
	public function redirect($url, $permanent = false)
	{
		if ($permanent)
		{
			$this->status = 301;
		}
		else if ($this->status < 300 && !$this->status !== 201)
		{
			$this->status = 302;
		}
		
		header('Location: '.$url, true, $this->status);
		exit;
	}
	
	/*
	 * Method: error
	 */
	public function error($status, $message = '')
	{
		$this->clean();
		$this->status = $status;
		
		if ($tpl = Aero::option('errors.template',false))
		{
			$this->render($tpl,array('status' => $this->status, 'message' => $message));
		}
		else
		{
			$this->output = $message;
		}
		
		$this->display();
	}
	
	/*
	 * Method: _compileCachingHeaders
	 *
	 */
	protected function _compileCachingHeaders()
	{
		if ($this->ttl == 0 || !$this->cache_on_client)
		{
			$this->setHeader('Expires', gmdate('D, d M Y H:i:s', time() - 365 * 24 * 60 * 60).' GMT');
			$this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s').' GMT');
			$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
			$this->setHeader('Cache-Control', 'post-check=0, pre-check=0'); // MS proprietor extension
		}
		else
		{
			$cache_ttl = 60 * $this->ttl;
			$this->setHeader('Expires', gmdate('D, d M Y H:i:s', time() + $cache_ttl).' GMT');
			$this->setHeader('Cache-Control', 'max-age='.$cache_ttl.', '.($this->cache_on_proxy ? 'public' : 'private'));
			$this->setHeader('Cache-Control', 'post-check='.$cache_ttl.', pre-check='.($cache_ttl * 2),false); // MS proprietory extension
		}
	}
	
	/*
	 * Method: _gzip
	 *
	 */
	protected function _gzip($content, $level)
	{
		return $content;
		if ($level == 0 
			|| !isset($_SERVER['HTTP_ACCEPT_ENCODING'])
			|| strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip') === false
			|| !function_exists('gzencode')
			|| ini_get('zlib.output_compression'))
		{
			return $content;
		}
		
		$encoding = strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'x-gzip') !== false ?
			'x-gzip' : 'gzip';

		$this->setHeader('Vary','Accept-Encoding');
		$this->setHeader('Content-Encoding', $encoding);

		return gzencode($content, $level, FORCE_GZIP);
	}
}
